/* StatSystem.cs

	Infinite Rogue
	By Dan Alexander, Andy Law
	July, 2012

	Cross platform roguelike using C#, Mono, WinForms and GDI.
	
	LICENSE: New BSD License
*/

using System;
using System.Collections.Generic;

public class StatSystem{

    //Core
    public const string HP_MAX = "HP_MAX";
    public const string HP = "HP";
    public const string MP_MAX = "MAX_MP";
    public const string MP = "MP";
    
    // Basic
    public const string STR = "STR";
    public const string AGI = "AGI";
    public const string END = "END";
    public const string WIS = "WIS";
    public const string INT = "INT";

    public const string EXP = "EXP";
    public const string LVL = "LVL";

    // Distributable

    public const string STATPOINTS = "STATPOINTS";
    public const string SKILLPOINTS = "SKILLPOINTS";

    // Combat
    public const string DAMAGE = "DAMAGE";
    public const string ARMOR = "ARMOR";

    // Magic
    public const string P_FIRE = "P_FIRE";
    public const string P_WATER = "P_WATER";
    public const string P_AIR = "P_AIR";
    public const string P_EARTH = "P_EARTH";
    public const string P_DARK = "P_DARK";
    public const string P_LIGHT = "P_LIGHT";
    public const string R_FIRE = "R_FIRE";
    public const string R_WATER = "R_WATER";
    public const string R_AIR = "R_AIR";
    public const string R_EARTH = "R_EARTH";
    public const string R_DARK = "R_DARK";
    public const string R_LIGHT = "R_LIGHT";

    // Equipment
    public const string E_LEFTHAND = "E_LEFTHAND";
    public const string E_RIGHtHAND = "E_RIGHtHAND";
    public const string E_HEAD = "E_HEAD";
    public const string E_NECK = "E_NECK";
    public const string E_BODY = "E_BODY";
    public const string E_LEGS = "E_LEGS";
    public const string E_WAIST = "E_WAIST";
    public const string E_GLOVE = "E_GLOVE";
    public const string E_RING = "E_RING";
    public const string E_FEET = "E_FEET";

    // Skills

    public const string ATTACK = "ATTACK";
    public const string DODGE = "DODGE";
    public const string STUN = "STUN";
    public const string CRITICAL = "CRITICAL";
    public const string PARRY = "PARRY";
    public const string SEARCH = "SEARCH";
    public const string REGEN = "REGEN";

    // Statuses

    public const string STUNNED = "STUNNED";
    public const string POISONED = "POISONED";
    public const string VULNERABLE = "VULNERABLE";
    public const string STIFF = "STIFF";
    public const string MUTE = "MUTE";
    public const string ALERT = "ALERT";

	private Dictionary<string,int> _stats;

	//create the default stat system
	public StatSystem(){
		_stats = new Dictionary<string,int>();
        
        this[LVL] = 1;
        this[EXP] = 0;

        this[HP_MAX] = 100;
        this[HP] = 100;
        this[MP_MAX] = 100;
        this[MP] = 100;
        
        this[STR] = 10;
        this[END] = 10;
        this[AGI] = 10;
        this[WIS] = 10;
        this[INT] = 10;

        this[P_FIRE] = 100;
        this[P_WATER] = 100;
        this[P_AIR] = 100;
        this[P_EARTH] = 100;
        this[P_DARK] = 100;
        this[P_LIGHT] = 100;

        this[R_FIRE] = 100;
        this[R_WATER] = 100;
        this[R_AIR] = 100;
        this[R_EARTH] = 100;
        this[R_DARK] = 100;
        this[R_LIGHT] = 100;

        this[ATTACK] = 10;
        this[DODGE] = 10;

        this.StatChanged += new StatChangedEventHandler(EquipmentChangedHandler);
        this.StatChanging += new StatChangedEventHandler(StatusChangeEventHandler);
	}

    private static HashSet<string> equipmentStats;
    private static HashSet<string> statusStats;
    private static HashSet<string> statPointStats;
    private static HashSet<string> skillPointStats;

    public static bool IsStatusStat(string stat)
    {
        if (statusStats == null)
        {
            statusStats = new HashSet<string>();
            statusStats.Add(STUNNED);
            statusStats.Add(POISONED);
            statusStats.Add(VULNERABLE);
            statusStats.Add(STIFF);
            statusStats.Add(MUTE);
            statusStats.Add(ALERT);
        }
        return statusStats.Contains(stat);
    }

    public static bool IsEquipmentStat(string stat)
    {
        if (equipmentStats == null)
        {
            equipmentStats = new HashSet<string>();
            equipmentStats.Add(E_LEFTHAND);
            equipmentStats.Add(E_RIGHtHAND);
            equipmentStats.Add(E_HEAD);
            equipmentStats.Add(E_NECK);
            equipmentStats.Add(E_BODY);
            equipmentStats.Add(E_LEGS);
            equipmentStats.Add(E_WAIST);
            equipmentStats.Add(E_GLOVE);
            equipmentStats.Add(E_RING);
            equipmentStats.Add(E_FEET);
        }
        return equipmentStats.Contains(stat);
    }

    public static bool IsStatpointStat(string stat)
    {
        if (statPointStats == null)
        {
            statPointStats = new HashSet<string>();
            statPointStats.Add(STR);
            statPointStats.Add(END);
            statPointStats.Add(AGI);
            statPointStats.Add(WIS);
            statPointStats.Add(INT);
        }
        return statPointStats.Contains(stat);
    }

    public static bool IsSkillStat(string stat)
    {
        if (skillPointStats == null)
        {
            skillPointStats = new HashSet<string>();
            skillPointStats.Add(PARRY);
            skillPointStats.Add(CRITICAL);
            skillPointStats.Add(ATTACK);
            skillPointStats.Add(DODGE);
            skillPointStats.Add(SEARCH);
            skillPointStats.Add(REGEN);
            skillPointStats.Add(STUN);
        }
        return skillPointStats.Contains(stat);
    }

    /// <summary>
    /// Prevents decreasing of status affect. Use method DecrementStatuses instead.
    /// </summary>
    /// <param name="stat"></param>
    /// <param name="oldVal"></param>
    /// <param name="newVal"></param>
    void StatusChangeEventHandler(string stat, int oldVal, ref int newVal)
    {
        if (IsStatusStat(stat))
        {
            if (newVal <= oldVal) newVal = oldVal;
            if (newVal <= 0) newVal = 0;
        }
    }

    void EquipmentChangedHandler(string stat, int oldVal, ref int newVal)
    {
        if (IsEquipmentStat(stat))
        {
            
        }
    }

    public void DecrementStatuses()
    {
        foreach (string s in statusStats)
        {
            if (_stats.ContainsKey(s) && _stats[s] > 0)
            {
                _stats[s] -= 1;
            }
        }
    }
	
    public int this[string index]  
    {  
        get   
        {
            if (!_stats.ContainsKey(index.ToUpper())) return 0;
            // use indexto retrieve and return another value. 
			return _stats[index.ToUpper()];
        }  
        set   
        {
            if (index != index.ToUpper())
            {
                index = index.ToUpper();
            }

            // use index and value to set the value somewhere.   
            if (!_stats.ContainsKey(index))
            {
                _stats.Add(index, 0);
            }

            int oldVal = _stats[index];

            // if no value change requested, don't fire events
            if (oldVal != value)
            {
                // fire stat changing before change
                if (StatChanging != null)
                {
                    StatChanging(index, oldVal, ref value);
                }

                // if the new value wasn't set to the old value, make change
                if (oldVal != value)
                {
                    _stats[index] = value;
                    // notify changed
                    if (StatChanged != null)
                    {                        
                        StatChanged(index, oldVal, ref value);
                    }
                }
            }
        }  
    }

    /// <summary>
    /// Permanently define a min and max for a stat. When the value is passed in, if it is beyond the limit, it stays at the limit.
    /// </summary>
    /// <param name="stat">stat name</param>
    /// <param name="min">min value</param>
    /// <param name="max">max value</param>
    /// <remarks>No way to undefine a limit! Don't use for a limit that can change.</remarks>
    public void DefineLimits(string stat, int min, int max)
    {
        StatChanging += new StatChangedEventHandler((string s, int o, ref int n) =>
            {
                if (s == stat)
                {
                    if (n < min) n = min;
                    if (n > max) n = max;
                }
            });
    }
  
    public delegate void StatChangedEventHandler(string stat, int oldVal, ref int newVal);

    /// <summary>
    /// Fired after the stat value has changed and lets you know what it was and what it is.
    /// </summary>
    public event StatChangedEventHandler StatChanged;

    /// <summary>
    /// Fires before the stat value changes and allows you to change the new value
    /// </summary>
    public event StatChangedEventHandler StatChanging;
}